// --------------------------------------------------------------------------------------------------------------------
// <copyright file="Assert.cs" company="CSLib.Net Team">
//   Copyright (c) Roman "4ux-nbIx" Novitsky 2010
// </copyright>
// <summary>
//   Defines assertion methods.
// </summary>
// --------------------------------------------------------------------------------------------------------------------

#define DEBUG

namespace CSLib.Net.Diagnostics
{
  #region Namespace Imports

  using System;
  using System.Collections.Generic;
  using System.Diagnostics;
  using System.Globalization;
  using System.Linq;
  using System.Reflection;

  using Annotations;

  #endregion

  // ReSharper disable ValueAnalysis

  /// <summary>
  /// Defines assertion methods.
  /// </summary>
  [DebuggerStepThrough]
  [DebuggerNonUserCode]
  [UsedImplicitly(ImplicitUseKindFlags.Default, ImplicitUseTargetFlags.WithMembers)]
  public static class Assert
  {
    #region Public Methods

    /// <summary>
    /// Asserts that the specified argument is not <c>null</c>.
    /// </summary>
    /// <param name="argument">
    /// An argument value to check.
    /// </param>
    /// <param name="argumentName">
    /// A name of an argument.
    /// </param>
    [Conditional("DEBUG")]
    [AssertionMethod]
    public static void ArgumentNotNull(
      [AssertionCondition(AssertionConditionType.IS_NOT_NULL)] object argument, 
      [InvokerParameterName] [NotNull] string argumentName)
    {
      IsNotNull(argument, "{0} is null.", argumentName);
    }


    /// <summary>
    /// Asserts that the specified argument is not <c>null</c> or empty.
    /// </summary>
    /// <param name="value">
    /// The value to check.
    /// </param>
    /// <param name="argumentName">
    /// Name of the argument.
    /// </param>
    [Conditional("DEBUG")]
    [AssertionMethod]
    public static void ArgumentNotNullOrEmpty(
      [AssertionCondition(AssertionConditionType.IS_NOT_NULL)] string value, 
      [InvokerParameterName] [NotNull] string argumentName)
    {
      string detailMessage = string.Format(CultureInfo.InvariantCulture, "{0} is null or empty.", argumentName);

      IsNotNullOrEmpty(value, detailMessage);
    }


    /// <summary>
    /// Asserts that the specified argument value is not <c>null</c> or white space.
    /// </summary>
    /// <param name="value">
    /// The value to check.
    /// </param>
    /// <param name="argumentName">
    /// Name of the argument.
    /// </param>
    [Conditional("DEBUG")]
    [AssertionMethod]
    public static void ArgumentNotNullOrWhiteSpace(
      [AssertionCondition(AssertionConditionType.IS_NOT_NULL)] string value, 
      [InvokerParameterName] [NotNull] string argumentName)
    {
      string detailMessage = string.Format(CultureInfo.InvariantCulture, "{0} is null or whitespace.", argumentName);

      IsNotNullOrWhiteSpace(value, detailMessage);
    }


    /// <summary>
    /// Asserts that the method was called by static method of <paramref name="ownerType"/>.
    /// </summary>
    /// <param name="ownerType">
    /// Type for check.
    /// </param>
    [Conditional("DEBUG")]
    public static void CalledFromStaticContext([NotNull] Type ownerType)
    {
      StackFrame methodFrame;
      bool ok = IsCalledFromStaticContext(ownerType, out methodFrame);

      string message = string.Format(
        CultureInfo.InvariantCulture, 
        "Method {0} can be called only in static constructor and for type itself.", 
        methodFrame.GetMethod());

      IsTrue(ok, message);
    }


    /// <summary>
    /// Emits the specified error message.
    /// </summary>
    /// <param name="messageFormat">
    /// The message format.
    /// </param>
    /// <param name="messageArguments">
    /// An array of message arguments to format.
    /// </param>
    [Conditional("DEBUG")]
    [StringFormatMethod("messageFormat")]
    public static void Fail(
      [NotNull] string messageFormat, 
      [CanBeNull] params object[] messageArguments)
    {
      var message = AssertionHelper.GetDetailMessage(messageFormat, messageArguments);

#if WINDOWS && !MONO
      Debug.Fail(message);
#else
      Debug.Assert(false, message);
#endif
    }


    /// <summary>
    /// Asserts whether the specified condition is <c>false</c>.
    /// </summary>
    /// <param name="condition">
    /// The evaluation condition.
    /// </param>
    /// <param name="messageFormat">
    /// A <see cref="System.String"/> containing a message format.
    /// </param>
    /// <param name="messageArguments">
    /// An array of message arguments to format.
    /// </param>
    [Conditional("DEBUG")]
    [AssertionMethod]
    [StringFormatMethod("messageFormat")]
    public static void IsFalse(
      [AssertionCondition(AssertionConditionType.IS_FALSE)] bool condition, 
      [NotNull] string messageFormat, 
      [CanBeNull] params object[] messageArguments)
    {
      var detailMessage = AssertionHelper.GetDetailMessage(messageFormat, messageArguments);

      IsTrue(!condition, detailMessage);
    }


    /// <summary>
    /// Asserts whether specified object is not <c>null</c>.
    /// </summary>
    /// <param name="value">
    /// An <see cref="System.Object"/> to check.
    /// </param>
    /// <param name="messageFormat">
    /// A composite format <see cref="string"/>.
    /// </param>
    /// <param name="messageArguments">
    /// An array of message arguments to format.
    /// </param>
    [Conditional("DEBUG")]
    [AssertionMethod]
    [StringFormatMethod("messageFormat")]
    public static void IsNotNull(
      [AssertionCondition(AssertionConditionType.IS_NOT_NULL)] object value, 
      [NotNull] string messageFormat, 
      [CanBeNull] params object[] messageArguments)
    {
      string detailMessage = AssertionHelper.GetDetailMessage(messageFormat, messageArguments);

      IsTrue(value != null, detailMessage);
    }


    /// <summary>
    /// Asserts that the specified string is not <c>null</c> or empty.
    /// </summary>
    /// <param name="value">
    /// A <see cref="System.String"/> value to check.
    /// </param>
    /// <param name="messageFormat">
    /// A message format to display.
    /// </param>
    /// <param name="messageArguments">
    /// An array of message arguments to format.
    /// </param>
    [Conditional("DEBUG")]
    [AssertionMethod]
    [StringFormatMethod("messageFormat")]
    public static void IsNotNullOrEmpty(
      [AssertionCondition(AssertionConditionType.IS_NOT_NULL)] string value, 
      [NotNull] string messageFormat, 
      [CanBeNull] params object[] messageArguments)
    {
      string detailMessage = AssertionHelper.GetDetailMessage(messageFormat, messageArguments);

      IsTrue(!string.IsNullOrEmpty(value), detailMessage);
    }


    /// <summary>
    /// Asserts that the specified string is not <c>null</c> or white space.
    /// </summary>
    /// <param name="value">
    /// A <see cref="System.String"/> value to check.
    /// </param>
    /// <param name="messageFormat">
    /// A message to display.
    /// </param>
    /// <param name="messageArguments">
    /// An array of message arguments to format.
    /// </param>
    [Conditional("DEBUG")]
    [AssertionMethod]
    [StringFormatMethod("messageFormat")]
    public static void IsNotNullOrWhiteSpace(
      [AssertionCondition(AssertionConditionType.IS_NOT_NULL)] string value, 
      [NotNull] string messageFormat, 
      [CanBeNull] params object[] messageArguments)
    {
      string detailMessage = AssertionHelper.GetDetailMessage(messageFormat, messageArguments);

      IsTrue(!value.IsNullOrWhiteSpace(), detailMessage);
    }


    /// <summary>
    /// Asserts whether specified object is <c>null</c>.
    /// </summary>
    /// <param name="value">
    /// An <see cref="System.Object"/> to check.
    /// </param>
    /// <param name="messageFormat">
    /// A composite format <see cref="string"/>.
    /// </param>
    /// <param name="messageArguments">
    /// An array of message arguments to format.
    /// </param>
    [Conditional("DEBUG")]
    [AssertionMethod]
    [StringFormatMethod("messageFormat")]
    public static void IsNull(
      [AssertionCondition(AssertionConditionType.IS_NULL)] [CanBeNull] object value, 
      [NotNull] string messageFormat, 
      [CanBeNull] params object[] messageArguments)
    {
      string detailMessage = AssertionHelper.GetDetailMessage(messageFormat, messageArguments);

      IsTrue(value == null, detailMessage);
    }


    /// <summary>
    /// Asserts that the specified string is <c>null</c> or empty.
    /// </summary>
    /// <param name="value">
    /// A <see cref="System.String"/> value to check.
    /// </param>
    /// <param name="messageFormat">
    /// A message to display.
    /// </param>
    /// <param name="messageArguments">
    /// An array of message arguments to format.
    /// </param>
    [Conditional("DEBUG")]
    [AssertionMethod]
    [StringFormatMethod("messageFormat")]
    public static void IsNullOrEmpty(
      [CanBeNull] string value, [NotNull] string messageFormat, [CanBeNull] params object[] messageArguments)
    {
      string detailMessage = AssertionHelper.GetDetailMessage(messageFormat, messageArguments);

      IsTrue(string.IsNullOrEmpty(value), detailMessage);
    }


    /// <summary>
    /// Asserts that the specified string is <c>null</c> or white space.
    /// </summary>
    /// <param name="value">
    /// A <see cref="System.String"/> value to check.
    /// </param>
    /// <param name="messageFormat">
    /// A message to display.
    /// </param>
    /// <param name="messageArguments">
    /// An array of message arguments to format.
    /// </param>
    [Conditional("DEBUG")]
    [AssertionMethod]
    [StringFormatMethod("messageFormat")]
    public static void IsNullOrWhiteSpace(
      [CanBeNull] string value, [NotNull] string messageFormat, [CanBeNull] params object[] messageArguments)
    {
      string detailMessage = AssertionHelper.GetDetailMessage(messageFormat, messageArguments);

      IsTrue(value.IsNullOrWhiteSpace(), detailMessage);
    }


    /// <summary>
    /// Asserts that the specified object is of specified type.
    /// </summary>
    /// <param name="instance">
    /// An instance of a <see cref="System.Object"/>.
    /// </param>
    /// <param name="type">
    /// An instance of a <see cref="System.Type"/>.
    /// </param>
    /// <param name="messageFormat">
    /// A <see cref="System.String"/> containing composite detail message format.
    /// </param>
    /// <param name="messageArguments">
    /// An array of message arguments to format.
    /// </param>
    [Conditional("DEBUG")]
    [StringFormatMethod("detailMessageFormat")]
    public static void IsOfType(
      object instance, 
      [NotNull] Type type, 
      [NotNull] string messageFormat, 
      [CanBeNull] params object[] messageArguments)
    {
      string detailMessage = AssertionHelper.GetDetailMessage(messageFormat, messageArguments);

      if (instance == null)
      {
        Fail(detailMessage);
      }
      else
      {
        IsTrue(instance.GetType() == type, detailMessage);
      }
    }


    /// <summary>
    /// Asserts whether the specified condition is <c>true</c>.
    /// </summary>
    /// <param name="condition">
    /// The evaluation condition.
    /// </param>
    /// <param name="messageFormat">
    /// A <see cref="System.String"/> containing a message format.
    /// </param>
    /// <param name="messageArguments">
    /// An array of message arguments to format.
    /// </param>
    [Conditional("DEBUG")]
    [AssertionMethod]
    [StringFormatMethod("messageFormat")]
    public static void IsTrue(
      [AssertionCondition(AssertionConditionType.IS_TRUE)] bool condition, 
      [NotNull] string messageFormat, 
      [CanBeNull] params object[] messageArguments)
    {
      if (condition)
      {
        return;
      }

      var detailMessage = AssertionHelper.GetDetailMessage(messageFormat, messageArguments);

      Fail(detailMessage);
    }


    /// <summary>
    /// Asserts the identifier validity.
    /// </summary>
    /// <param name="identifier">
    /// An identifier to check.
    /// </param>
    /// <param name="detailMessageFormat">
    /// A <see cref="System.String"/> containing composite detail message format.
    /// </param>
    /// <param name="messageArguments">
    /// An array of message arguments to format.
    /// </param>
    [Conditional("DEBUG")]
    [StringFormatMethod("detailMessageFormat")]
    public static void ValidIdentifier(
      [NotNull] string identifier, [NotNull] string detailMessageFormat, [CanBeNull] params object[] messageArguments)
    {
      // TODO <RNovitsky, 11.02.2009>: use regexp :)
      string detailMessage = AssertionHelper.GetDetailMessage(detailMessageFormat, messageArguments);

      IsTrue(!identifier.Contains(@" "), detailMessage);
      IsTrue(!identifier.Contains(@"."), detailMessage);
      IsTrue(!identifier.Contains(@"\"), detailMessage);
      IsTrue(!identifier.Contains(@"/"), detailMessage);
    }


    internal static bool IsCalledFromStaticContext(Type ownerType, out StackFrame methodFrame)
    {
      var trace = new StackTrace();

      methodFrame = trace.GetFrame(1);

#if WINDOWS_PHONE
      Type realOwnerType = ownerType;
#else
      Type realOwnerType = ownerType.IsGenericType ? ownerType.GetGenericTypeDefinition() : ownerType;
#endif

      List<MethodBase> realOwnerConstructorInfos =
        realOwnerType.GetConstructors(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static).Cast<MethodBase>().ToList();

      bool ok = false;

      for (int i = 2; i < trace.FrameCount; i++)
      {
        StackFrame callerFrame = trace.GetFrame(i);

        if (!realOwnerConstructorInfos.Contains(callerFrame.GetMethod()))
        {
          continue;
        }

        ok = true;
        break;
      }

      return ok;
    }

    #endregion
  }
}